package ru.usu.gv.utils.imports.dynamic;

import ru.usu.gv.utils.Pair;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author spupyrev
 * 25.02.2010
 */
public class MultipleNicknames
{
	private static int PERIOD = 7;
	private static int COUNT = 160;

	private static int W_SIZE = 20;
	private static double LIMIT = 0.20;

	public static void main(String argc[])
	{
		//VNCData data = new VNCData();
		EnronData data = new EnronData();
		List<Map<Integer, Map<Integer, Double>>> edges = new ArrayList();
		for (int i = 0; i < COUNT; i++)
		{
			edges.add(data.getEdges(i * PERIOD, (i + 1) * PERIOD));
		}

		Map<Pair<Integer, Integer>, Double> foundPairs = new HashMap();
		Map<Pair<Integer, Integer>, Integer> foundPairsStartDays = new HashMap();

		int startDay = 100, endDay = 150;
		for (int i = startDay; i + W_SIZE <= endDay; i++)
		{
			processDays(edges, data, i, foundPairs, foundPairsStartDays);
			System.out.println("processed " + (i + 1) + " days (" + COUNT + ")");
		}
		outputResult(data, foundPairs, foundPairsStartDays);
	}

	private static void outputResult(BaseData data, final Map<Pair<Integer, Integer>, Double> foundPairs, Map<Pair<Integer, Integer>, Integer> foundPairsStartDays)
	{
		System.out.println("found " + foundPairs.size() + " pairs");
		List<Pair<Integer, Integer>> pairs = new ArrayList(foundPairs.keySet());
		Collections.sort(pairs, new Comparator<Pair<Integer, Integer>>()
		{
			public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2)
			{
				return foundPairs.get(o2).compareTo(foundPairs.get(o1));
			}
		});
		for (Pair<Integer, Integer> pr : pairs)
		{
			int id1 = pr.getFirst();
			int id2 = pr.getSecond();
			double val = foundPairs.get(pr);
			int stDay = foundPairsStartDays.get(pr);
			int enDay = foundPairsStartDays.get(pr) + W_SIZE;
			String ss = String.format("Found pair (%.3f): " + data.getName(id1) + "->" + data.getName(id2), val);
			String s = "(" + id1 + " - " + id2 + "), " + stDay + " - " + enDay + ")";
			System.out.println(ss + s);
		}
		System.out.println("done");
	}

	private static void processDays(List<Map<Integer, Map<Integer, Double>>> allEdges, BaseData data, int startDay, Map<Pair<Integer, Integer>, Double> foundPairs,
			Map<Pair<Integer, Integer>, Integer> foundPairsStartDays)
	{
		Map<Integer, Integer> indexes = new HashMap();
		for (int i = startDay; i < startDay + W_SIZE; i++)
		{
			Map<Integer, Map<Integer, Double>> edges = allEdges.get(i);
			for (Integer id : edges.keySet())
				indexes.put(id, indexes.size());
		}

		for (int id1 : indexes.keySet())
			for (int id2 : indexes.keySet())
				if ( data.getName(id1).endsWith("@enron.com") && !data.getName(id1).endsWith("@enron.com") )
				//if (id1 < id2)
				{
					System.out.println("checking...");
					//check id1&id2
					double val = calculateSimilarity(allEdges, startDay, id1, id2);
					if (val > LIMIT)
					{
						Pair<Integer, Integer> pr = new Pair(id1, id2);
						if (!foundPairs.containsKey(pr) || foundPairs.get(pr) < val)
						{
							foundPairs.put(pr, val);
							foundPairsStartDays.put(pr, startDay);
						}
						//String s = "(" + id1 + " - " + id2 + "), " + startDay + " - " + endDay + ")";
						//System.out.println("Found pair (" + val + "): " + data.getName(id1) + "->" + data.getName(id2) + s);
					}
					/*if (id1 == 65 && id2 == 218)
					{
						String s = "(" + id1 + " - " + id2 + "), " + startDay + " - " + (startDay + W_SIZE) + ")";
						System.out.println("Test pair (" + val + "): " + data.getName(id1) + "->" + data.getName(id2) + s);
					}*/
				}
	}

	private static double calculateSimilarity(List<Map<Integer, Map<Integer, Double>>> edges, int startDay, int id1, int id2)
	{
		double sum = 0, cnt = 0;
		double common = 0, f1 = 0, f2 = 0;
		for (int i = 0; i < W_SIZE; i++)
		{
			Map<Integer, Map<Integer, Double>> ei = edges.get(startDay + i);
			if (ei.containsKey(id1) && ei.containsKey(id2))
				common++;
			if (ei.containsKey(id1) && !ei.containsKey(id2))
				f1++;
			if (!ei.containsKey(id1) && ei.containsKey(id2))
				f2++;

		}

		double mult = (f1 / (f1 + common)) * (f2 / (f2 + common));
		assert (mult <= 1.0);
		if (mult < LIMIT)
			return 0;

		double[] edgeSum1 = new double[W_SIZE];
		double[] edgeSum2 = new double[W_SIZE];
		double sum1 = 0, sum2 = 0;
		for (int i = 0; i < W_SIZE; i++)
		{
			Map<Integer, Map<Integer, Double>> ei = edges.get(startDay + i);
			edgeSum1[i] = calculateEdges(ei.get(id1));
			edgeSum2[i] = calculateEdges(ei.get(id2));

			sum1 += edgeSum1[i];
			sum2 += edgeSum2[i];
		}

		for (int i = 0; i < W_SIZE; i++)
		{
			Map<Integer, Map<Integer, Double>> ei = edges.get(startDay + i);
			for (int j = 0; j < W_SIZE; j++)
			{
				Map<Integer, Map<Integer, Double>> ej = edges.get(startDay + j);
				if (ei.containsKey(id1) && ej.containsKey(id2))
				{
					double sim = calculateSimilarity(ei.get(id1), ej.get(id2));
					double v1 = edgeSum1[i];
					double v2 = edgeSum2[j];
					sim = sim * v1 * v2 / (sum1 * sum2);
					sum += sim;
					cnt++;
				}
			}
		}

		if (cnt == 0)
			return 0;

		assert (sum <= 1.0);
		//sum *= (f1 * f2) / (1 + common);
		//sum /= cnt;
		sum *= mult;
		//sum *= (window - common) / window;

		return sum;
	}

	private static double calculateEdges(Map<Integer, Double> map)
	{
		if (map == null)
			return 0;
		double res = 0;
		for (Integer key : map.keySet())
			res += map.get(key);

		return res;
	}

	private static double calculateSimilarity(Map<Integer, Double> ini, Map<Integer, Double> inj)
	{
		double mn = 0, all = 0;
		Set<Integer> neigIn = new HashSet();
		neigIn.addAll(ini.keySet());
		neigIn.addAll(inj.keySet());
		for (Integer key : neigIn)
		{
			double s1 = 0, s2 = 0;
			if (ini.containsKey(key))
				s1 = ini.get(key);
			if (inj.containsKey(key))
				s2 = inj.get(key);

			mn += Math.min(s1, s2);
			all += Math.max(s1, s2);
		}

		double alpha = Math.log10(Math.max(2, mn)) / 4.0;

		mn /= all;
		mn *= alpha;
		assert (mn <= 1.0);
		return mn;
	}
}
